package org.bdware.repository.main;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.log4j.Logger;
import org.bdware.doip.core.crypto.GlobalCertifications;
import org.bdware.doip.core.doipMessage.DoipMessage;
import org.bdware.doip.core.doipMessage.DoipMessageFactory;
import org.bdware.doip.core.doipMessage.DoipResponseCode;
import org.bdware.doip.core.utils.GlobalConfigurations;
import org.bdware.doip.endpoint.doipClient.SyncDOAClient;
import org.bdware.doip.endpoint.doipServer.*;
import org.bdware.doip.implementation.DoIndexerLuceneImpl;
import org.bdware.irp.client.GlobalIrpClient;
import org.bdware.irp.client.IrpClientImpl;
import org.bdware.irp.exception.IrpClientException;
import org.bdware.irp.stateinfo.DoipServiceStateInfo;
import org.bdware.repository.DoipServiceConfig;
import org.bdware.repository.handler.BDRepositoryHandler;
import org.bdware.repository.storage.BDRepoStorage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.rocksdb.RocksDBException;

import java.io.IOException;
import java.security.Security;
import java.util.List;
import java.util.Scanner;

public class BDRepository {
    static Logger logger = Logger.getLogger(BDRepository.class);
    static String configFilePath = "repoConfig.json";

    static BDRepositoryHandler repositoryHandler;
    static DoipServiceInfo info;
    static BDRepoStorage store;
    static DoIndexerLuceneImpl indexer;
    public static DoipServiceConfig doipServiceConfig;

    public static void main(String[] args) {
        if(args.length>0){
            configFilePath = args[0];
        }
        doipServiceConfig = DoipServiceConfig.loadFromConfigFile(configFilePath);
        if(doipServiceConfig != null && doipServiceConfig.listeners != null && !doipServiceConfig.listeners.equals("")) {
            if (doipServiceConfig.IRSAddress != null) GlobalConfigurations.IRSAddress = doipServiceConfig.IRSAddress;
            if (doipServiceConfig.userID != null) GlobalConfigurations.userID = doipServiceConfig.userID;
            if (doipServiceConfig.jwkPath != null) GlobalConfigurations.JWKPath = doipServiceConfig.jwkPath;
            if (doipServiceConfig.jksPath != null) GlobalConfigurations.JKSPath = doipServiceConfig.jksPath;
            if (doipServiceConfig.jksPassword != null) GlobalConfigurations.JKSPassword = doipServiceConfig.jksPassword;
        } else {
            logger.info("cannot find config file, using default config");
            doipServiceConfig = DoipServiceConfig.createDefaultConfig();
        }

        try {
            init();
        } catch (Exception e) {
            logger.error("load key file failed");
            return ;
        }

        info = new DoipServiceInfo(
                doipServiceConfig.repoID,
                doipServiceConfig.userID,
                doipServiceConfig.type,
                new Gson().fromJson(doipServiceConfig.listeners, new TypeToken<List<DoipListenerInfo>>() {
                }.getType()));
        info.serviceName = doipServiceConfig.serviceName;
        info.serviceDescription = doipServiceConfig.serviceDescription;

        DoipServiceStateInfo repoHR = info.toServiceHandleRecord();
        logger.debug(new Gson().toJson(repoHR));

        try{
            if (repoHR.identifier == null || repoHR.identifier.equals("") || repoHR.identifier.equals("unregistered")) {
                String newHandle = GlobalIrpClient.getGlobalClient().register(repoHR);
                logger.info("[HandleService] response from LHS: " + newHandle);
                doipServiceConfig.repoID = info.id = newHandle;
                doipServiceConfig.save(configFilePath);
            } else {
                new Thread(()->{
                    String LHSResp = null;
                    try {
                        LHSResp = GlobalIrpClient.getGlobalClient().reRegister(repoHR);
                    } catch (IrpClientException e) {
                        e.printStackTrace();
                    }
                    logger.info("[HandleService] update state info in LRS: " + LHSResp);
                }).start();
            }
        }catch (IrpClientException e){
            logger.error("register to IRS failed, repo need an IRS");
            return ;
        }catch (IOException ie){
            logger.warn("save config file failed, path: " + configFilePath);
            return ;
        }

        DoipServer server = DoipServerImpl.createDoipServer(info);

        try {
            store = new BDRepoStorage(doipServiceConfig.repoPath);
            indexer = new DoIndexerLuceneImpl(doipServiceConfig.indexPath);
            repositoryHandler = new BDRepositoryHandler(info, store, indexer);
        } catch (RocksDBException e) {
            logger.error("create repo handler error");
            e.printStackTrace();
            return;
        }
        server.setRepositoryHandler(repositoryHandler);
        new Thread(server::start).start();

        Scanner reader = new Scanner(System.in);
        try {
            SyncDOAClient client = SyncDOAClient.getInstance();
            while(true){
                showHelp();
                DoipMessage req;
                DoipMessage resp;
                String[] cmd = reader.nextLine().split(" ");
                if(cmd.length == 0) continue;
                switch (cmd[0]){
                    case "join":
                        req = new DoipMessageFactory.DoipMessageBuilder()
                                .createRequest(cmd[1],"Op.Join")
                                .addAttributes("repoID",info.id)
                                .addAttributes("repoUrl",info.listenerInfos.get(0).url)
                                .create();
                        logger.info("-try to join registry: " + cmd[1]);
                        resp = client.sendRaw(req,cmd[1]);
                        logger.info("-join response code: " + resp.header.parameters.response.getName());
                        logger.info("-join response body: " + resp.body.getDataAsJsonString());
                        if(resp.header.parameters.response == DoipResponseCode.Success){
                            repositoryHandler.addLevel2RemoteRegistry(cmd[1],client.getDoipClient(cmd[1]).getRepoUrl());
                        }


                        break;
                    case "quit":
                        req = new DoipMessageFactory.DoipMessageBuilder()
                                .createRequest(cmd[1],"Op.Quit")
                                .addAttributes("repoID",info.id)
                                .addAttributes("repoUrl",info.listenerInfos.get(0).url)
                                .create();
                        logger.info("-try to join registry: " + cmd[1]);
                        resp = client.sendRaw(req,cmd[1]);
                        logger.info("-join response code: " + resp.header.parameters.response.getName());
                        logger.info("-join response body: " + resp.body.getDataAsJsonString());
                        repositoryHandler.removeRemoteRegs(cmd[1]);
                        break;
                    case "trustee":
                        String registryID = cmd[1];
                        DoipServiceStateInfo regState = DoipServiceStateInfo.fromStateInfoBase(
                                GlobalIrpClient.getGlobalClient().resolve(registryID));
                        List<DoipListenerInfo> listenerInfos = new Gson().fromJson(regState.getListeners(), new TypeToken<List<DoipListenerInfo>>() {
                        }.getType());
                        repositoryHandler.addLevel2RemoteRegistry(registryID,listenerInfos.get(0).url);
                        logger.info("trustee registry: " + registryID + "; url: " + listenerInfos.get(0).url);
                        break;
                    case "exit":
                        server.stop();
                        System.exit(0);
                }
            }
        } catch (IrpClientException e) {
            e.printStackTrace();
        }


    }

    private static void showHelp() {
        System.out.println("Repository can dynamically connect to a registry usage:");
        System.out.println("       join RegistryID");
        System.out.println("       trustee RegistryID");
        System.out.println("       quit RegistryID");
        System.out.println("       exit");
    }

    public static void init() throws Exception {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
        if(GlobalConfigurations.JWKPath != null){
            GlobalCertifications.loadKeysFromJWK(GlobalConfigurations.JWKPath);
        }else if(GlobalConfigurations.JKSPath != null){
            GlobalCertifications.loadKeysFromJKS(GlobalConfigurations.JKSPath, GlobalConfigurations.JKSPassword);
        }else{
            logger.warn("no keys found, exit");
            System.exit(0);
        }

        GlobalIrpClient.useTcpIrpClient(GlobalCertifications.jwKeyPair
                , GlobalConfigurations.userID
                , GlobalConfigurations.IRSAddress);



        ((IrpClientImpl) GlobalIrpClient.getGlobalClient()).connect(GlobalConfigurations.IRSAddress);

    }

    public static BDRepoStorage getStore(){
        return store;
    }
    public static DoIndexerLuceneImpl getIndexer(){
        return indexer;
    }
    public static BDRepositoryHandler getRepositoryHandler(){
        return repositoryHandler;
    }
    public static DoipServiceInfo getInfo(){
        return info;
    }
}
